package com.wanna.common.doc.scanner.impl;

import com.alibaba.fastjson.JSON;
import com.wanna.common.doc.annotation.ApiInterface;
import com.wanna.common.doc.annotation.ApiMethod;
import com.wanna.common.doc.annotation.ApiParam;
import com.wanna.common.doc.entity.*;
import com.wanna.common.doc.scanner.ApiScanner;
import com.wanna.common.doc.util.ClassUtil;
import com.wanna.common.doc.util.InstanceUtil;
import com.wanna.common.doc.util.JsonUtil;
import com.wanna.common.lang.StringUtil;
import com.wanna.common.util.PackageUtil;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.alibaba.fastjson.serializer.SerializerFeature.*;

/**
 * 默认的 api 接口 扫描器
 *
 * @author wanna
 * @since 2018-11-25
 */
public class DefaultApiScanner implements ApiScanner {


    @Override
    public List<ApiInfo> scan(ScannerConfig scannerConfig, String packageName) throws Exception {
        List<Class> classes = filterControllers(PackageUtil.getClasses(packageName));
        List<ApiInfo> result = new LinkedList<>();
        for (Class cls : classes) {
            if (needScanner(cls, scannerConfig)) {
                ApiInterface annotation = AnnotationUtils.findAnnotation(cls, ApiInterface.class);
                List<MethodInfo> methods = getMethodsInfo(cls);
                ApiInfo apiInfo = new ApiInfo();
                apiInfo.setName(annotation.name());
                apiInfo.setDescription(annotation.description());
                apiInfo.setMethods(methods);
                result.add(apiInfo);
            }
        }
        return result;
    }

    /**
     * 是否需要扫描
     *
     * @param clazz         class
     * @param scannerConfig config
     * @return true or false
     */
    private boolean needScanner(Class clazz, ScannerConfig scannerConfig) {
        ScannerType scannerType = scannerConfig.getScannerType();
        if (scannerType == ScannerType.All) {
            return true;
        }
        boolean needInterface = clazz.isInterface() && scannerType != ScannerType.OnlyInstance;
        boolean needInstance = !clazz.isInterface() && scannerType != ScannerType.OnlyInterface;
        return needInterface || needInstance;
    }

    /**
     * 获取所有接口方法
     *
     * @param cls class
     * @return methodInfo
     * @throws Exception exception
     */
    private List<MethodInfo> getMethodsInfo(Class cls) throws Exception {
        List<MethodInfo> result = new ArrayList<>();
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            ApiMethod annotation = method.getAnnotation(ApiMethod.class);

            MethodInfo methodInfo = new MethodInfo();
            methodInfo.setName(annotation.name());
            methodInfo.setPath(annotation.path());
            methodInfo.setRequestType(annotation.type().name());
            methodInfo.setParams(getMethodParam(method));
            methodInfo.setReturnSample(getReturnSample(annotation));
            methodInfo.setDescription(annotation.description());
            result.add(methodInfo);
        }
        return result;
    }

    /**
     * 获取返回值
     *
     * @param method apiMethod
     * @return json
     */
    private String getReturnSample(ApiMethod method) throws Exception {
        String json = method.returnJson();
        Class cls = method.returnClass();
        if (cls != Object.class) {
            return getJsonWithCommentSample(cls, method.collectionType(), false);
        } else {
            return StringUtil.isEmpty(json) ? "" : json;
        }
    }


    /**
     * 获取方法参数
     *
     * @param method method
     * @return paramInfo
     */
    private List<ParamInfo> getMethodParam(Method method) throws Exception {
        List<ParamInfo> result = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (Parameter param : parameters) {
            ApiParam apiParam = param.getAnnotation(ApiParam.class);

            ParamInfo paramInfo = buildParamInfo(param, apiParam);

            result.add(paramInfo);
        }
        return result;
    }

    /***
     * 构建 paramInfo
     * @param param param
     * @param apiParam apiParam
     * @return paramInfo
     * @throws Exception exception
     */
    private ParamInfo buildParamInfo(Parameter param, ApiParam apiParam) throws Exception {
        ParamInfo paramInfo = new ParamInfo();
        paramInfo.setName(apiParam.name());
        paramInfo.setRequired(apiParam.required());
        paramInfo.setType(param.getType().getSimpleName());
        paramInfo.setDescription(apiParam.description());
        paramInfo.setJsonSample(getParamSample(apiParam));
        return paramInfo;
    }

    /**
     * 获取参数 json 示例
     *
     * @param apiParam apiParam
     * @return json sample
     * @throws Exception exception
     */
    private String getParamSample(ApiParam apiParam) throws Exception {
        Class cls = apiParam.paramClass();
        if (cls != Object.class) {
            return getJsonWithCommentSample(cls, apiParam.collectionType(), true);
        } else {
            return "";
        }
    }

    /**
     * 获取 json 示例(带注释)
     *
     * @param cls          class
     * @param collection   是都是集合类型
     * @param needRequired 是否需要必填注释
     * @return json sample
     * @throws Exception exception
     */
    @SuppressWarnings("unchecked")
    private String getJsonWithCommentSample(Class cls, boolean collection, boolean needRequired) throws Exception {
        Object instance = InstanceUtil.newInstance(cls, collection);
        Field[] fields = ClassUtil.getDeclaredFields(cls);
        String sample = JSON.toJSONString(instance,
                PrettyFormat, WriteMapNullValue,
                WriteNullNumberAsZero, WriteNullListAsEmpty,
                WriteNullStringAsEmpty, WriteNullBooleanAsFalse);
        return JsonUtil.putJsonComment(fields, sample, needRequired);
    }

    /**
     * 匹配注解了 ApiInterface 的类
     *
     * @param classes classes
     * @return List
     */
    private List<Class> filterControllers(Class[] classes) {
        return Stream.of(classes)
                .filter(this::matchedClassAnnotation)
                .collect(Collectors.toList());
    }

    /**
     * 是否匹配到 对应注解
     *
     * @param clazz class
     * @return matched return true,else return false
     */
    private boolean matchedClassAnnotation(Class clazz) {
        return Objects.nonNull(clazz.getAnnotation(ApiInterface.class));
    }
}
